/*
Copyright (C) 2008 raohmaru <http://www.raohmaru.com>

This file is part of Raohmaru AS3 Framework.

Raohmaru AS3 Framework is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Raohmaru AS3 Framework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Raohmaru AS3 Framework.  If not, see <http://www.gnu.org/licenses/>.
*/

package jp.raohmaru.media
{
import flash.events.*;
import flash.media.*;
import flash.net.URLRequest;

/**
 * La clase SoundFX añade nuevas funcionalidades a la clase Sound y simplifica su utilización.
 * @author raohmaru
 * @version 1.0
 * @see http://help.adobe.com/es_ES/FlashPlatform/reference/actionscript/3/flash/media/Sound.html
 * @example
<listing version="3.0">
import jp.raohmaru.media.SoundFX;
import jp.raohmaru.media.filters.PitchFilter;

var pitch :PitchFilter = new PitchFilter(.5, 4096);

var sfx :SoundFX = new SoundFX("assets/music.mp3");
    sfx.filters = [pitch];
    sfx.play(0, -1);
</listing>
 */
public class SoundFX extends Sound
{
	private var	_dummy :Sound,
				_sch :SoundChannel,
				_stf :SoundTransform,
				_playing :Boolean,
				_paused :Boolean = true,
				_loaded :Boolean = true,
				_position :Number = -1,
				_position_byte :Number = -1,
				_loops :int,
				_loop_count :int,
				_start_time :Number,
				_filters :Array;
	
	/**
	 * Obtiene un objeto SoundChannel que se utiliza para controlar el sonido.
	 * @see http://help.adobe.com/es_ES/FlashPlatform/reference/actionscript/3/flash/media/SoundChannel.html
	 */
	public function get soundChannel() :SoundChannel
	{
		return _sch;
	}
	
	/**
	 * El objeto SoundTransform asignado al canal de sonido.
	 * @see http://help.adobe.com/es_ES/FlashPlatform/reference/actionscript/3/flash/media/SoundTransform.html
	 */
	public function get soundTransform() :SoundTransform
	{
		return _sch ? _sch.soundTransform : null;
	}
	public function set soundTransform( value :SoundTransform ) :void
	{
		_stf = value;
		if(_sch)
			_sch.soundTransform = _stf;
	}
	
	/**
	 * Indica (en milisegundos) el punto actual que se está reproduciendo en el archivo de sonido, o el punto en el que
	 * está detenido.
	 */
	public function get position() :Number
	{
		return _sch ? _sch.position : 0;
	}
	
	/**
	 * Obtiene o define el volumen, con valores comprendidos entre 0 (silencio) y 1 (máximo volumen).
	 */
	public function get volume() :Number
	{
		return _stf ? _stf.volume : 1;
	}
	public function set volume( value :Number ) :void
	{
		if(_sch)
		{
			_stf.volume = value;
			_sch.soundTransform = _stf;
		}
	}
	
	/**
	 * El desplazamiento lateral del sonido, de izquierda a derecha, con valores comprendidos entre -1
	 * (desplazamiento lateral total hacia la izquierda) y 1 (desplazamiento lateral total hacia la derecha).
	 */
	public function get pan() :Number
	{
		return _stf ? _stf.pan : 0;
	}
	public function set pan( value :Number ) :void
	{
		if(_sch)
		{
			_stf.pan = value;
			_sch.soundTransform = _stf;
		}
	}
	
	/**
	 * Matriz que contiene todos los objetos de filtro actualmente asociados con el objeto de sonido.
	 * El paquete jp.raohmaru.media.filters contiene varias clases que definen filtros específicos que puede utilizar.<br />
	 * Para modificar un objeto de filtro existente no es necesario copiar el conjunto <code>filters</code>:
 	 * @example
<listing version="3.0">
import jp.raohmaru.media.SoundFX;
import jp.raohmaru.media.filters.NoiseFilter;

var filter :PitchFilter = new NoiseFilter();

var sfx :SoundFX = new SoundFX();
    sfx.filters = [filter];
    sfx.play(0, -1);

filter.amplitude = 1;  // Afectará a todos los objetos SoundFX que contengan este filtro
</listing>
 	 * La propiedad <code>filters</code> solo está disponible para Flash Player 10 o superior.
 	 * @playerversion 10
	 */
	public function get filters() :Array
	{
		return _filters;
	}	
	public function set filters(value :Array) : void
	{
		_filters = value;
		
		if(_filters.length > 0)
		{
			if(!_dummy)
				_dummy = new Sound();
		}
		else
		{
			_dummy.removeEventListener(SampleDataEvent.SAMPLE_DATA, sampleDataHandler);
			_dummy = null;
		}
	}
	
	/**
	 * Indica si el medio se está reproduciendo.
	 */
	public function get playing() :Boolean
	{
		return _playing;
	}
	
	/**
	 * Indica si el medio está pausado (no se reproduce).
	 */
	public function get paused() :Boolean
	{
		return _paused;
	}

	/**
	 * Indica si el archivo externo se ha cargado correctamente.
	 */
	public function get loaded() :Boolean
	{
		return _loaded;
	}
	
	
	/**
	 * Crea un objeto SoundFX nuevo. Si se especifica una URL de un archivo de sonido externo, se llamará automáticamente
	 * al método <code>load()</code>.
	 * @param url La URL que señala a un archivo MP3 externo.
	 * @param context Objeto de contexto SoundLoader opcional que puede definir el tiempo del búfer (número mínimo
	 * de milisegundos de datos MP3 almacenados en el búfer del objeto Sound) y puede especificar si la aplicación
	 * debe buscar un archivo de política entre dominios para poder cargar el sonido.
	 */
	public function SoundFX( url :String = null, context :SoundLoaderContext = null )
	{
		var req :URLRequest = (url != null) ? new URLRequest(url) : null;
		super( req, context );
		_stf = new SoundTransform();
		_filters = [];
	}
	
	/**
	 * Genera un nuevo objeto SoundChannel para reproducir el sonido.
	 * @param startTime La posición inicial en la que debería iniciarse la reproducción, expresada en milisegundos.
	 * @param loops Define el número de veces que un sonido vuelve al valor <code>startTime</code> antes de que
	 * el canal de sonido detenga la reproducción.<br />Si el valor es -1, se reproducirá indefinidamente.
	 * @param sndTransform El objeto SoundTransform inicial asignado al canal de sonido.
	 */
	override public function play( startTime :Number = 0, loops :int = 0, sndTransform :SoundTransform = null ) :SoundChannel
	{
		if(_playing) return _sch;
		
		_start_time = (startTime == -1) ? (_position > -1 ? _position : 0) : startTime;
		_position = -1;
		
		if(startTime > -1)	_loops = loops;		
		if(_sch)			_sch.removeEventListener(Event.SOUND_COMPLETE, onSndComplete);		
		if(sndTransform)	_stf = sndTransform;
		
		// Normal sound play
		if(_filters.length == 0)
		{
			_sch = super.play(_start_time, (_loops == -1 ? 0 : _loops), _stf);
			_sch.addEventListener(Event.SOUND_COMPLETE, onSndComplete);
		}
		// Play with filters
		else
		{
			if(startTime > -1)	_loop_count = 0;
			if(_loaded)			playFX(_start_time);
		}
		
		_playing = true;
		_paused = false;
		
		return _sch;
	}
	
	/**
	 * Comienza la reproducción aplicando filtros a la salida del sonido.
	 */
	private function playFX(startTime :Number = 0) :void
	{
		var startByte :Number = (_position_byte == -1) ? ( startTime / length ) * bytesTotal : _position_byte;
		if(startByte != startByte) startByte = 0;
		
		for(var i:int=0; i<_filters.length; i++)
			_filters[i].reset( startByte );
		
		if(!_dummy.hasEventListener(SampleDataEvent.SAMPLE_DATA))
			_dummy.addEventListener(SampleDataEvent.SAMPLE_DATA, sampleDataHandler);
		
		if(_sch) _sch.stop();
		_sch = _dummy.play(0, 0, _stf);
	}
	
	/**
	 * Pausa la reproducción del sonido.
	 */
	public function pause() :void
	{
		if(_paused) return;
		
		var pos :Number = _sch ? _sch.position : -1;
		if(_filters.length > 0)
			var pos_byte :Number = _position_byte;
			
		stop();
		
		_position = pos;
		if(_filters.length > 0)
			_position_byte = pos_byte;
	}
	
	/**
	 * Detiene la reproducción del sonido y desplaza la cabeza lector a <code>startTime</code>.
	 */
	public function stop() :void
	{
		_position = -1;
		_position_byte = -1;
		
		if(_sch == null) return;
		
		_sch.removeEventListener(Event.SOUND_COMPLETE, onSndComplete );
		_sch.stop();
		
		if(_dummy)
			_dummy.removeEventListener(SampleDataEvent.SAMPLE_DATA, sampleDataHandler);
		
		_playing = false;
		_paused = true;
	}
	
	/**
	 * @private
	 */
	override public function load( stream :URLRequest, context :SoundLoaderContext = null ) :void
	{
		if(stream != null)
		{
			_loaded = false;
			addEventListener(Event.COMPLETE, onLoadComplete);
		}
		super.load(stream, context);
	}
	
	/**
	 * Inicia la carga de un archivo MP3 externo desde la URL especificada. Este método es un atajo a la función
	 * <code>load()</code> sin la necesidad de crear un objeto URLRequest.
	 * @param stream Una URL que señala a un archivo MP3 externo.
	 * @param context Objeto de contexto SoundLoader opcional que puede definir el tiempo del búfer (número mínimo
	 * de milisegundos de datos MP3 almacenados en el búfer del objeto Sound) y puede especificar si la aplicación
	 * debe buscar un archivo de política entre dominios para poder cargar el sonido.
	 * @see http://help.adobe.com/es_ES/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#load()
	 */
	public function loadURL( url :String, context :SoundLoaderContext = null ) :void
	{
		load(new URLRequest(url), context);
	}
	
	/**
	 * @private
	 */
	override public function close() :void
	{
		super.close();
		stop();
		_stf = null;
		removeEventListener(Event.COMPLETE, onLoadComplete);
	}



	private function onSndComplete ( e :Event ) :void
	{
		stop();
		
		if(_loops == -1)
			play(_start_time, -1);
		else
			dispatchEvent(e);
	}
	
	private function onLoadComplete( e :Event ) :void
	{
		_loaded = true;
		removeEventListener(Event.COMPLETE, onLoadComplete);
		
		if(_playing && _filters.length > 0)
			playFX(_start_time);
	}
	
	private function sampleDataHandler(e: SampleDataEvent): void
	{
		if(!_loaded) return;
		
		_position_byte = e.position;
		
		var r :int,
			read :int = -1;
		for(var i:int=0; i<_filters.length; i++)
		{
			r = _filters[i].apply(this, e.data);
			if(read == -1 || r < read)
				read = r;
		}
		
		// No more data to process
		if(read == 0)
		{
			_position_byte = -1;
			
			if(_loops == -1 || _loops > 0 && _loop_count++ < _loops)
			{
				playFX(_start_time);
			}
			else
			{
				// Calling stop() before dispatching the Event.SOUND_COMPLETE crash Flash CS5.5, so do stop here
				_dummy.removeEventListener(SampleDataEvent.SAMPLE_DATA, sampleDataHandler);				
				_playing = false;
				_paused = true;
				dispatchEvent(new Event(Event.SOUND_COMPLETE));
				_sch.stop();
			}
		}
	}
}
}